// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="AggregationProcessor.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the AggregationProcessor type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace ScrumForTeamSystem.EventService.AggregationService
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;

    using DataObjects;

    using Infrastructure;
    using Infrastructure.Interfaces;

    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    using ScrumForTeamSystem.EventService.AggregationService.Properties;
    using ScrumForTeamSystem.EventService.Services;

    /// <summary>
    /// Defines the AggregationProcessor type.
    /// </summary>
    [Export(typeof(ITfsEventProcessor))]
    public class AggregationProcessor : WorkItemChangedEventProcessorBase<AggregateRule>
    {
        /// <summary>
        /// The work item updater.
        /// </summary>
        private readonly UpdateWorkItem updater;

        /// <summary>
        /// Initializes a new instance of the <see cref="AggregationProcessor"/> class.
        /// </summary>
        public AggregationProcessor() : this(DependencyResolver.GetImplementation<IWorkItemStoreRepository>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AggregationProcessor"/> class.
        /// </summary>
        /// <param name="itemStoreRepository">The item store repository.</param>
        private AggregationProcessor(IWorkItemStoreRepository itemStoreRepository) : base(itemStoreRepository)
        {
            this.updater = new UpdateWorkItem();

            this.updater.ProcessMessage += s => this.OnMessage(this.updater, s);
        }

        /// <summary>
        /// Processes the changed links.
        /// </summary>
        /// <param name="workItemChangedEvent">The work item changed event.</param>
        /// <param name="workItemStore">The work item store.</param>
        /// <param name="allRules">All rules.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated in assert method"), 
        System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The called method will throw the exception if not a handled type.")]
        protected override void ProcessLinkChanges(WorkItemChangedEvent workItemChangedEvent, WorkItemStore workItemStore, IEnumerable<AggregateRule> allRules)
        {
            AssertEventAndStoreAreNotNull(workItemChangedEvent, workItemStore);

            var allLinks = workItemChangedEvent.AllChangedLinkIds();

            if (allLinks.Count() == 0)
            {
                return;
            }

            foreach (var linkId in allLinks)
            {
                var workItem = workItemStore.GetWorkItem(linkId);

                var rules = allRules.Where(r => r.WorkItemType.Target.Equals(workItem.Type.Name)).ToArray();

                if (rules.Count() == 0)
                {
                    continue;
                }

                try
                {
                    this.updater.Execute(workItem, rules);
                }
                catch (Exception ex)
                {
                    this.OnExceptionMessage(this, ex);
                }
            }
        }

        /// <summary>
        /// Processes the matched rules.
        /// </summary>
        /// <param name="workItemChangedEvent">The work item changed event.</param>
        /// <param name="workItemStore">The work item store.</param>
        /// <param name="matchedRules">The matched rules.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated in assert method"), 
        System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception method rethrows the exception if not the expected type.")]
        protected override void ProcessMatchedRules(WorkItemChangedEvent workItemChangedEvent, WorkItemStore workItemStore, IEnumerable<AggregateRule> matchedRules)
        {
            AssertEventAndStoreAreNotNull(workItemChangedEvent, workItemStore);

            var sourceWorkItemId = workItemChangedEvent.GetWholeNumberFieldValue(ConstantStrings.WorkItemIdCoreField);

            try
            {
                var sourceWorkItem = workItemStore.GetWorkItem(sourceWorkItemId);

                var workItemRulesMap = new Dictionary<int, KeyValuePair<WorkItem, List<AggregateRule>>>();

                // Map all rules that target the source work item.
                var targetRules = matchedRules.Where(r => r.WorkItemType.Target.Equals(sourceWorkItem.Type.Name)).ToArray();
                if (targetRules.Count() != 0)
                {
                    workItemRulesMap.Add(
                            sourceWorkItem.Id,
                            new KeyValuePair<WorkItem, List<AggregateRule>>(sourceWorkItem, new List<AggregateRule>(targetRules)));
                }

                // Map all the target work items and matching rules.
                foreach (var rule in matchedRules.Except(targetRules))
                {
                    var workItems = sourceWorkItem.GetLinkedTargets(rule)
                            .Union(sourceWorkItem.GetUnlinkedTargets(rule, workItemChangedEvent));

                    foreach (var workItem in workItems)
                    {
                        KeyValuePair<WorkItem, List<AggregateRule>> workItemRuleListPair;

                        if (!workItemRulesMap.TryGetValue(workItem.Id, out workItemRuleListPair))
                        {
                            workItemRuleListPair = new KeyValuePair<WorkItem, List<AggregateRule>>(workItem, new List<AggregateRule>());
                            workItemRulesMap.Add(workItem.Id, workItemRuleListPair);
                        }

                        workItemRuleListPair.Value.Add(rule);
                    }
                }

                // Update all the target work items
                foreach (var map in workItemRulesMap.Values)
                {
                    this.updater.Execute(map.Key, map.Value);
                }
            }
            catch (Exception ex)
            {
                this.OnExceptionMessage(this, ex);
            }
        }

        /// <summary>
        /// Loads the rules.
        /// </summary>
        /// <returns>A collection of rules.</returns>
        protected override IRulesCollection<AggregateRule> LoadRules()
        {
            var rules = ResourceHelper.LoadRules();

            this.OnMessage(this, Resources.String001);

            return rules;
        }
    }
}